#pragma once
#include "ArcNode.hpp"

namespace Kama
{
    template <typename Key, typename Value>
    class ArcLru
    {
    public:
        ArcLru(int capactiy, int transformThreshold) : m_capacity(capactiy), m_ghostCapacity(capactiy)
        , m_transformThreshold(transformThreshold)
        {
            initList();
        }

        bool put(const Key& key, const Value& value)
        {
            if (m_capacity <= 0)
            {
                return false;
            }
            std::lock_guard<std::mutex> lgd(mtx);
            auto it = mainCache.find(key);
            if (it != mainCache.end())
            {
                updateNode(it->second, value);
                return true;
            }
            addNewNode(key, value);
            return true;
        }

        bool get(const Key& key, Value& value, bool& shouldTransform)
        {
            std::lock_guard<std::mutex> lgd(mtx);
            auto it = mainCache.find(key);
            if (it != mainCache.end())
            {
                shouldTransform = updateAccess(it->second);
                value = it->second->getValue();
                return true;
            }
            return false;
        }

        Value get(const Key& key)
        {
            Value value;
            bool shouldTransform = false;
            get(key, value, shouldTransform);
            return value;
        }

        bool checkGhost(const int& key)
        {
            auto it = ghostCache.find(key);
            if (it != ghostCache.end())
            {
                removeFromGhost(it->second);
                ghostCache.erase(it);
                return true;
            }
            return false;
        }

        void addCapacity()
        {
            ++m_capacity;
        }

        bool subtractCapacity()
        {
            if (m_capacity <= 0)
            {
                return false;
            }
            if (mainCache.size() == m_capacity)
            {
                removeLongestUnuse();
            }
            --m_capacity;
            return true;
        }
    private:
        void initList()
        {
            mainHead = std::make_shared<ArcNode<Key, Value>>();
            mainTail = std::make_shared<ArcNode<Key, Value>>();
            mainHead->m_next = mainTail;
            mainTail->m_pre = mainHead;

            ghostHead = std::make_shared<ArcNode<Key, Value>>();
            ghostTail = std::make_shared<ArcNode<Key, Value>>();
            ghostHead->m_next = ghostTail;
            ghostTail->m_pre = ghostHead;
        }

        void addNewNode(const Key& key, const Value& value)
        {
            if (mainCache.size() == m_capacity)
            {
                removeLongestUnuse();
            }
            std::shared_ptr<ArcNode<Key, Value>> newnode = std::make_shared<ArcNode<Key, Value>>(key, value);
            addToFront(newnode);
            mainCache.insert({key, newnode});
        }

        void addToFront(std::shared_ptr<ArcNode<Key, Value>> node)
        {
            node->m_pre = mainHead;
            node->m_next = mainHead->m_next;
            mainHead->m_next->m_pre = node;
            mainHead->m_next = node;
        }

        void addToGhost(std::shared_ptr<ArcNode<Key, Value>> node)
        {
            node->resetAccess();
            node->m_pre = ghostHead;
            node->m_next = ghostHead->m_next;
            ghostHead->m_next->m_pre = node;
            ghostHead->m_next = node;
            ghostCache.insert({node->getKey(), node});
        }

        void removeFromMain(std::shared_ptr<ArcNode<Key, Value>> node)
        {
            if (!node->m_pre.expired() && node->m_next)
            {
                auto prev = node->m_pre.lock();
                prev->m_next = node->m_next;
                node->m_next->m_pre = node->m_pre;
                node->m_next = nullptr;
            }
        }

        void removeFromGhost(std::shared_ptr<ArcNode<Key, Value>> node)
        {
            if (!node->m_pre.expired() && node->m_next)
            {
                auto prev = node->m_pre.lock();
                prev->m_next = node->m_next;
                node->m_next->m_pre = node->m_pre;
                node->m_next = nullptr;
            }
        }

        void removeLongestUnuse()
        {
            std::shared_ptr<ArcNode<Key, Value>> node = mainTail->m_pre.lock();
            if (!node || node == mainHead)
            {
                return;
            }

            removeFromMain(node);
            mainCache.erase(node->getKey());

            if (ghostCache.size() == m_ghostCapacity)
            {
                removeOldestGhost();
            }
            addToGhost(node);
        }

        void moveToFront(std::shared_ptr<ArcNode<Key, Value>> node)
        {
            if (!node->m_pre.expired() && node->m_next)
            {
                auto prev = node->m_pre.lock();
                prev->m_next = node->m_next;
                node->m_next->m_pre = node->m_pre;
                node->m_next = nullptr;
            }
            addToFront(node);
        }

        void updateNode(std::shared_ptr<ArcNode<Key, Value>> node, const Value& value)
        {
            node->setValue(value);
            node->increseAccess();
            moveToFront(node);
        }

        bool updateAccess(std::shared_ptr<ArcNode<Key, Value>> node)
        {
            node->increseAccess();
            removeFromMain(node);
            addToFront(node);
            return node->getAccess() >= m_transformThreshold;
        }

        void removeOldestGhost()
        {
            std::shared_ptr<ArcNode<Key, Value>> oldestGhost = ghostTail->m_pre.lock();
            if (!oldestGhost || oldestGhost == ghostTail)
            {
                return;
            }
            removeFromGhost(oldestGhost);
            ghostCache.erase(oldestGhost->getKey());
        }
    private:
        std::unordered_map<Key, std::shared_ptr<ArcNode<Key, Value>>> mainCache;
        std::unordered_map<Key, std::shared_ptr<ArcNode<Key, Value>>> ghostCache;
        std::shared_ptr<ArcNode<Key, Value>> mainHead;
        std::shared_ptr<ArcNode<Key, Value>> mainTail;
        std::shared_ptr<ArcNode<Key, Value>> ghostHead;
        std::shared_ptr<ArcNode<Key, Value>> ghostTail;
        std::mutex mtx;
        int m_transformThreshold;
        int m_capacity;
        int m_ghostCapacity;
    };
}